import xml.etree.ElementTree as etree
import sys

class XmlActivityPatternWrapper:
    '''class for wrapping uml activity diagrams patterns'''
    idcounter = 0

    def __init__(self, root):
        self.root = root

    def getId(self):
        self.idcounter += 1
        return str(self.idcounter - 1)

    def sequence(self, edge):
        xmiattr = ".//{0}[@{{http://schema.omg.org/spec/XMI/2.1}}{1}='{2}']"

        sourceid = edge.attrib['source']
        sources = self.root.findall(xmiattr.format('packagedElement', 'id', sourceid))
        if len(sources) != 1:
            return False
        targetid = edge.attrib['target']
        targets = self.root.findall(xmiattr.format('packagedElement', 'id', targetid))
        if len(targets) != 1:
            return False

        edgessourcesource = self.root.findall(".//edge[@source='{0}']".format(sourceid))
        if len(edgessourcesource) != 1:
            return False
        edgestargettarget = self.root.findall(".//edge[@target='{0}']".format(targetid))
        if len(edgestargettarget) != 1:
            return False

        # new xml element
        newid = self.getId()
        wrapper = etree.Element('packagedElement', attrib={'{http://schema.omg.org/spec/XMI/2.1}id':newid, '{http://schema.omg.org/spec/XMI/2.1}type':'formula'})
        ns = '({0})=><>({1}),~({0})=>~<>({1}),[]~(({0})&({1}))'
        wrapper.attrib['name'] = ns.format(sources[0].attrib['name'], targets[0].attrib['name'])
        self.root.insert(-1, wrapper)

        #change old id for id of new element
        for node in self.root.findall(".//edge[@source='{0}']".format(targetid)):
            node.attrib['source'] = newid
        for node in self.root.findall(".//edge[@target='{0}']".format(sourceid)):
            node.attrib['target'] = newid

        #remove old elements
        self.root.remove(edge)
        self.root.remove(sources[0])
        self.root.remove(targets[0])

        return True

    def branching(self, decision):
        #maybe: extend to check more than two branches
        
        incomings = decision.findall('incoming')
        if len(incomings) != 1:
            return False
        outgoings = decision.findall('outgoing')
        if len(outgoings) !=    2:
            return False

        incomingsidrefs = [ incomings[0].attrib['{http://schema.omg.org/spec/XMI/2.1}idref'] ]
        outgoingsidrefs = [ val.attrib['{http://schema.omg.org/spec/XMI/2.1}idref'] for val in outgoings ]
        if len(outgoingsidrefs) != 2:
            return False

        edgessources = [ self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(incomingsidrefs[0])) ]
        if len(edgessources[0]) != 1:
            return False
        edgetobranches = [ self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(val)) for val in outgoingsidrefs ]
        if len(edgetobranches) != 2:
            return False
        if len(edgetobranches[0]) != 1:
            return False
        if len(edgetobranches[1]) != 1:
            return False
                        
        sources = [ self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgessources[0][0].attrib['source'])) ]
        if len(sources[0]) != 1:
            return False
        branches = [ self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(val[0].attrib['target'])) for val in edgetobranches ]
        if len(branches) != 2:
            return False
        if len(branches[0]) != 1:
            return False            
        if len(branches[1]) != 1:
            return False        
            
        sourceid = sources[0][0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']
        branchesid = [ val[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id'] for val in branches ]
        if len(branchesid) != 2:
            return False
            
        unwantededgesfromsource = [ self.root.findall(".//edge[@source='{0}']".format(sourceid)) ]
        if len(unwantededgesfromsource[0]) != 1:
            return False
        unwantededgestobranches = [ self.root.findall(".//edge[@target='{0}']".format(val)) for val in branchesid ]
        for unwantededgestobranch in unwantededgestobranches:
            if len(unwantededgestobranch) != 1:
                return False

        targetedges = [ self.root.findall(".//edge[@source='{0}']".format(val)) for val in branchesid ]
        if len(edgetobranches) != len(targetedges):
            return False

        targets = [ self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(val[0].attrib['target'])) for val in targetedges ]
        if not len(targets) or not len(targets[0]):
            return False
        if len(targets) != 2:
            return False
        targetsid = targets[0][0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']
        for targetNode in targets[1:]:
            if len(targetNode) != 1:
                return False
            targetNodeId = targetNode[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']
            if targetNodeId != targetsid:
                return False

        newid = self.getId()
        wrapper = etree.Element('packagedElement', attrib={'{http://schema.omg.org/spec/XMI/2.1}id':newid, '{http://schema.omg.org/spec/XMI/2.1}type':'formula'})
        ns  = '({0}) => (<>({1}) & ~<>({2})) | (~<>({1}) | <>({2})),'
        ns += '~({0}) => ~((<>({1}) & ~<>({2})) | (~<>({1}) & <>({2}))),'
        ns += '({1}) | ({2}) => <>({3}) , ~(({1}) | ({2})) => ~<>({3}) , []~(({0}) & ({3})),'
        ns += '[]~(({1}) & ({2})) , []~(({0}) & (({1}) | ({2}))) , []~((({1}) | ({2})) & ({3}))'
        wrapper.attrib['name'] = ns.format(sources[0][0].attrib['name'], branches[0][0].attrib['name'], branches[1][0].attrib['name'], targets[0][0].attrib['name'])
        self.root.insert(-1, wrapper)

        edgessourcesource = self.root.findall(".//edge[@source='{0}']".format(sourceid))
        edgessourcetarget = self.root.findall(".//edge[@target='{0}']".format(sourceid))
        edgestargetsource = self.root.findall(".//edge[@source='{0}']".format(targetsid))
        edgestargettarget = self.root.findall(".//edge[@target='{0}']".format(targetsid))

        for node in edgestargetsource:
            node.attrib['source'] = newid
        for node in edgessourcetarget:
            node.attrib['target'] = newid

        self.root.remove(edgessources[0][0])
        for node in self.root.findall('./packagedElement'):
            if len(list(node)):
                for edges in edgetobranches:
                    for edge in edges:
                        node.remove(edge)
                break
        self.root.remove(sources[0][0])
        for activites in branches:
            for avtivity in activites:
                self.root.remove(avtivity)
        for edges in targetedges:
            for edge in edges:
                self.root.remove(edge)
        self.root.remove(targets[0][0])
        for node in self.root.findall('./packagedElement'):
            if len(list(node)):
                node.remove(decision) # also removes incoming and outgoing
                break

        return True

    def concurency(self, fork):

        # get elements inside concurency and check if it a concurency
        incomings = fork.findall('incoming')
        if len(incomings) != 1:
            #print('error incomings ', len(incomings))
            return False
        outgoings = fork.findall('outgoing')
        if len(outgoings) != 2:
            #print('error outgoings ', len(outgoings))
            return False

        incomingsidrefs = [ incomings[0].attrib['{http://schema.omg.org/spec/XMI/2.1}idref'] ]
        outgoingsidrefs = [ val.attrib['{http://schema.omg.org/spec/XMI/2.1}idref'] for val in outgoings ]

        edgessources = [ self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(incomingsidrefs[0])) ]
        edgetobranches = [ self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(val)) for val in outgoingsidrefs ]

        sources = [ self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgessources[0][0].attrib['source'])) ]
        branches = [ self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(val[0].attrib['target'])) for val in edgetobranches ]

        sourceid = sources[0][0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']
        branchesid = [ val[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id'] for val in branches ]

        deforkedges = [ self.root.findall(".//edge[@source='{0}']".format(val)) for val in branchesid ]

        deforks = [ self.root.findall(".//node[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(val[0].attrib['target'])) for val in deforkedges ]
        if not len(deforks) or not len(deforks[0]):
            return False
        deforkid = deforks[0][0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']

        for defork in deforks:
            for node in defork:
                if deforkid != node.attrib['{http://schema.omg.org/spec/XMI/2.1}id']:
                    #print('error targets: different target in concurency')
                    return False

        deforkoutgoing = deforks[0][0].findall('outgoing')
        #print(deforkoutgoing[0].attrib)

        targetedges = self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(deforkoutgoing[0].attrib['{http://schema.omg.org/spec/XMI/2.1}idref']))
        #print(targetedges)
        #print(targetedges[0].attrib)

        targets = self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(targetedges[0].attrib['target']))
        targetsid = targets[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']

        #todo: conditions if it is the concurency

        newid = self.getId()
        wrapper = etree.Element('packagedElement', attrib={'{http://schema.omg.org/spec/XMI/2.1}id':newid, '{http://schema.omg.org/spec/XMI/2.1}type':'formula'})
        #todo: concurency when more than two branches
        #formulabr = []
        #for branch in branches:
        #    formulabr += [ '({0})'.format(branch[0].attrib['name']) ]
        ns  = '({0}) => <>({1}) & <>({2}) , ~({0}) => ~(<>({1}) & <>({2})),'
        ns += '({1}) & ({2}) => <>({3}) , ~(({1}) & ({2})) => ~<>({3}),'
        ns += '[]~(({0}) & (({1}) | ({2}))) , []~((({1}) | ({2})) & ({3})) , []~(({0}) & ({3}))'
        wrapper.attrib['name'] = ns.format(sources[0][0].attrib['name'], branches[0][0].attrib['name'], branches[1][0].attrib['name'], targets[0].attrib['name'])
        self.root.insert(-1, wrapper)

        edgessourcesource = self.root.findall(".//edge[@source='{0}']".format(sourceid))
        edgessourcetarget = self.root.findall(".//edge[@target='{0}']".format(sourceid))
        edgestargetsource = self.root.findall(".//edge[@source='{0}']".format(targetsid))
        edgestargettarget = self.root.findall(".//edge[@target='{0}']".format(targetsid))

        for node in edgestargetsource:
            node.attrib['source'] = newid
        for node in edgessourcetarget:
            node.attrib['target'] = newid

        self.root.remove(edgessources[0][0])
        for node in self.root.findall('./packagedElement'):
            if len(list(node)):
                for edges in edgetobranches:
                    for edge in edges:
                        node.remove(edge)
                break
        self.root.remove(sources[0][0])
        for activites in branches:
            for avtivity in activites:
                self.root.remove(avtivity)
        for edges in deforkedges:
            for edge in edges:
                self.root.remove(edge)
        self.root.remove(targets[0])
        for node in self.root.findall('./packagedElement'):
            if len(list(node)):
                node.remove(fork) # also removes incoming and outgoing
                node.remove(deforks[0][0])
                node.remove(targetedges[0])
                break

        return True

    def loopwhile(self, decision):
        #print(decision.attrib)

        # get elements inside loopwhile and check if it a loopwhile
        incomings = decision.findall('incoming')
        if len(incomings) != 2:
            #print('error incomings ', len(incomings))
            return False
        outgoings = decision.findall('outgoing')
        if len(outgoings) != 2:
            #print('error outgoings ', len(outgoings))
            return False

        edgesincomings = [ self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']"
                                        .format(val.attrib['{http://schema.omg.org/spec/XMI/2.1}idref'])) for val in incomings]
        edgesoutgoings = [ self.root.findall(".//edge[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']"
                                        .format(val.attrib['{http://schema.omg.org/spec/XMI/2.1}idref'])) for val in outgoings]

        source = None
        looping = None
        if edgesincomings[0][0].attrib['target'] in [ edgesoutgoings[0][0].attrib['target'], edgesoutgoings[1][0].attrib['target'] ]:
            looping = self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgesincomings[0][0].attrib['source']))
            source = self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgesincomings[1][0].attrib['source']))
        else:
            looping =  self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgesincomings[1][0].attrib['source']))
            source = self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgesincomings[0][0].attrib['source']))

        #print(source)
        #print(looping)
        target = None
        if looping[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id'] == edgesoutgoings[0][0].attrib['target']:
            target = self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgesoutgoings[1][0].attrib['target']))
        else:
            target = self.root.findall(".//packagedElement[@{{http://schema.omg.org/spec/XMI/2.1}}id='{0}']".format(edgesoutgoings[0][0].attrib['target']))
        #print(target)

        sourceid = source[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']
        targetid = target[0].attrib['{http://schema.omg.org/spec/XMI/2.1}id']

        #todo: conditions if it is the loopWhile

        newid = self.getId()
        wrapper = etree.Element('packagedElement', attrib={'{http://schema.omg.org/spec/XMI/2.1}id':newid, '{http://schema.omg.org/spec/XMI/2.1}type':'formula'})
        ns  = '({0}) => <>({1}) , ~({0}) => ~<>({1}) '
        ns += '({1}) & c(({1})) => <>({2}) & ~<>({3}) , ~(({1}) & c(({1}))) => ~(<>({2}) & ~<>({3})),'
        ns += '({1}) & ~c(({1})) => ~<>({2}) & <>({3}) , ~(({1}) & ~c(({1}))) => ~(~<>({2}) & <>({3})),'
        ns += '({2}) => <>({1}) , ~({2}) => ~<>({1}),'
        ns += '[]~(({0}) & ({1})) , []~(({0}) & ({2})) , []~(({0}) & ({3})),'
        ns += '[]~(({1}) & ({2})) , []~(({1}) & ({3})) , []~(({2}) & ({3}))'
        wrapper.attrib['name'] = ns.format(target[0].attrib['name'], decision.attrib['name'], looping[0].attrib['name'], target[0].attrib['name'])
        self.root.insert(-1, wrapper)

        edgessourcesource = self.root.findall(".//edge[@source='{0}']".format(sourceid))
        edgessourcetarget = self.root.findall(".//edge[@target='{0}']".format(sourceid))
        edgestargetsource = self.root.findall(".//edge[@source='{0}']".format(targetid))
        edgestargettarget = self.root.findall(".//edge[@target='{0}']".format(targetid))

        for node in edgestargetsource:
            node.attrib['source'] = newid
        for node in edgessourcetarget:
            node.attrib['target'] = newid

        self.root.remove(source[0])
        self.root.remove(looping[0])
        self.root.remove(target[0])
        for edges in edgesincomings:
            for edge in edges:
                self.root.remove(edge)
        for node in self.root.findall('./packagedElement'):
            if len(list(node)):
                node.remove(decision) # also removes incoming and outgoing
                for edges in edgesoutgoings:
                    for edge in edges:
                        node.remove(edge)
                break

        return True
        
def validateTree(tree):
    # check if edge has only one element assigned to each end
    return

def checkIfEverythingWasDone(tree):
    # check if there are no edges left
    return

def analyzeTree(tree):
    model = tree.getroot().findall('{http://schema.omg.org/spec/UML/2.1}Model')[0]
    root = model.findall('packagedElement')[0]

    ap = XmlActivityPatternWrapper(root)

    patternDetected = True
    while patternDetected:
        patternDetected = False
        for decision in root.findall(".//packagedElement/node[@{http://schema.omg.org/spec/XMI/2.1}type='uml:DecisionNode']"):
            patternDetected |= ap.branching(decision)
            if not patternDetected:
                patternDetected |= ap.loopwhile(decision)

        for fork in root.findall(".//packagedElement/node[@{http://schema.omg.org/spec/XMI/2.1}type='uml:ForkNode']"):
            patternDetected |= ap.concurency(fork)

        for edge in root.findall('edge'):
            patternDetected |= ap.sequence(edge)
            
    checkIfEverythingWasDone(tree)

    for pe in root.findall('packagedElement'):
        if pe.attrib['{http://schema.omg.org/spec/XMI/2.1}type'] == 'formula':
            print(pe.attrib['name'])

if __name__ == '__main__':
    filesCount = len(sys.argv) - 1
    if filesCount == 0:
        print("no file given as argument!")
        exit();

    if filesCount != 1:
        print("### multiple files as arguments: ", len(sys.argv)-1)

    for arg in sys.argv[1:]:
        if filesCount != 1:
            print("### ", arg)

        tree = etree.parse(arg)
        validateTree(tree)
        analyzeTree(tree)

        if not filesCount != 1:
            tree.write('../python/tree.xml')
